package jeff.plugin.mybatis.code.visitor;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.comments.Comment;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.Name;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import jeff.plugin.mybatis.util.CollectionUtil;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class MergeVisitor extends VoidVisitorAdapter<Object> {

    protected CompilationUnit newCompilationUnit;
    protected Map<Name, ImportDeclaration> newImportDeclaration;

    private int headBodyDeclarationIndex = 0;

    public MergeVisitor(CompilationUnit compilationUnit){
        this.newCompilationUnit = compilationUnit;
    }

    @Override
    public void visit(ImportDeclaration oldImport, Object arg) {
        super.visit(oldImport, arg);
        if(newImportDeclaration == null){
            newImportDeclaration = newCompilationUnit.getImports().stream().collect(Collectors.toMap(ImportDeclaration::getName, ImportDeclaration->ImportDeclaration));
        }

        ImportDeclaration newImport = newImportDeclaration.get(oldImport.getName());
        if(newImport == null){
            newCompilationUnit.addImport(oldImport);
            newImportDeclaration.put(oldImport.getName(), oldImport);
        }
    }

    @Override
    public void visit(ClassOrInterfaceDeclaration oldClazz, Object arg) {
        super.visit(oldClazz, arg);
        Optional<ClassOrInterfaceDeclaration> newClassOrInterfaceDeclaration = newCompilationUnit.getClassByName(oldClazz.getNameAsString());
        if(!newClassOrInterfaceDeclaration.isPresent()){
            //内部类处理
            Optional<Node> parentNode = oldClazz.getParentNode();
            if(!parentNode.isPresent() || !(parentNode.get() instanceof ClassOrInterfaceDeclaration)){
                return;
            }
            ClassOrInterfaceDeclaration oldParentClazz = (ClassOrInterfaceDeclaration)parentNode.get();
            newClassOrInterfaceDeclaration = newCompilationUnit.getClassByName(oldParentClazz.getNameAsString());
            newClassOrInterfaceDeclaration.ifPresent(s -> s.getMembers().add(headBodyDeclarationIndex++, oldClazz));
            return;
        }
        ClassOrInterfaceDeclaration newClazz = newClassOrInterfaceDeclaration.get();

        //合并类注释
        Optional<Comment> oldComment = oldClazz.getComment();
        oldComment.ifPresent(s -> newClazz.setComment(oldComment.get()));

        //覆盖继承
        NodeList<ClassOrInterfaceType> extendTypes = oldClazz.getExtendedTypes();
        if(CollectionUtil.isNotEmpty(extendTypes) && CollectionUtil.isEmpty(newClazz.getExtendedTypes())){
            newClazz.setExtendedTypes(extendTypes);
        }

        //覆盖接口
        NodeList<ClassOrInterfaceType> implementedTypes = oldClazz.getImplementedTypes();
        if(CollectionUtil.isNotEmpty(implementedTypes) && CollectionUtil.isEmpty(newClazz.getImplementedTypes())){
            newClazz.setImplementedTypes(implementedTypes);
        }

        //覆盖注解
        NodeList<AnnotationExpr> annotationExprs = oldClazz.getAnnotations();
        if(CollectionUtil.isNotEmpty(annotationExprs)){
            newClazz.setAnnotations(annotationExprs);
        }

    }

    @Override
    public void visit(FieldDeclaration oldField, Object arg) {
        super.visit(oldField, arg);
        VariableDeclarator variableDeclarator = oldField.getVariable(0);
        String fieldName = variableDeclarator.getNameAsString();

        Optional<Node> parentNode = oldField.getParentNode();
        if(!parentNode.isPresent() || !(parentNode.get() instanceof ClassOrInterfaceDeclaration)){
            return;
        }
        ClassOrInterfaceDeclaration oldClazz = (ClassOrInterfaceDeclaration)parentNode.get();
        Optional<ClassOrInterfaceDeclaration> newClazzOptional = newCompilationUnit.getClassByName(oldClazz.getNameAsString());
        if(!newClazzOptional.isPresent()){
            return;
        }

        Optional<Comment> oldCommentOptional = oldField.getComment();
        Optional<FieldDeclaration> newFieldDeclarationOptional = newClazzOptional.get().getFieldByName(fieldName);

        //有注释且包含@mbggenerated,则检查注解
        if(oldCommentOptional.isPresent() && containMbgComment(oldCommentOptional.get().getContent())){
            if(newFieldDeclarationOptional.isPresent()){
                //覆盖注解
                NodeList<AnnotationExpr> annotationExprs = oldField.getAnnotations();
                if(CollectionUtil.isNotEmpty(annotationExprs)){
                    newFieldDeclarationOptional.get().setAnnotations(annotationExprs);
                }
            }
        }
        //无注释或者不包含@mbggenerated
        else{
            if(newFieldDeclarationOptional.isPresent()){
                //skip
            }
            else{
                newClazzOptional.get().getMembers().add(headBodyDeclarationIndex++, oldField);
            }
        }
    }

    @Override
    public void visit(MethodDeclaration oldMethodDeclaration, Object arg) {
        super.visit(oldMethodDeclaration, arg);
        String methodName = oldMethodDeclaration.getNameAsString();

        NodeList<Parameter> parameters = oldMethodDeclaration.getParameters();

        Optional<Node> parentNode = oldMethodDeclaration.getParentNode();
        if(!parentNode.isPresent() || !(parentNode.get() instanceof ClassOrInterfaceDeclaration)){
            return;
        }
        ClassOrInterfaceDeclaration oldClazz = (ClassOrInterfaceDeclaration)parentNode.get();
        Optional<ClassOrInterfaceDeclaration> newClazzOptional = newCompilationUnit.getClassByName(oldClazz.getNameAsString());
        if(!newClazzOptional.isPresent()){
            return;
        }

        Optional<Comment> oldCommentOptional = oldMethodDeclaration.getComment();
        List<MethodDeclaration> newMethods = newClazzOptional.get().getMethodsByName(methodName);

        MethodDeclaration newMethodDeclaration = findMethodDeclaration(newMethods, methodName, parameters);
        if(newMethodDeclaration == null){
            if(!oldCommentOptional.isPresent() || !containMbgComment(oldCommentOptional.get().getContent())) {
                newClazzOptional.get().getMembers().add(headBodyDeclarationIndex++, oldMethodDeclaration);
            }
        }
        else{
            NodeList<AnnotationExpr> oldAnnotations = oldMethodDeclaration.getAnnotations();
            if(CollectionUtil.isNotEmpty(oldAnnotations)){
                newMethodDeclaration.setAnnotations(oldAnnotations);
            }
        }
    }

    private MethodDeclaration findMethodDeclaration(List<MethodDeclaration> methods, String methodName, NodeList<Parameter> parameters){
        for (MethodDeclaration methodDeclaration : methods){
            if(!methodName.equals(methodDeclaration.getNameAsString())){
                continue;
            }

            NodeList<Parameter> newParameterTypes = methodDeclaration.getParameters();
            if(parameters.size() == 0 && newParameterTypes.size() == 0){
                return methodDeclaration;
            }
            if(parameters.size() == newParameterTypes.size()){
                for(int i = 0; i < newParameterTypes.size(); i++){
                    Parameter newParameter = newParameterTypes.get(i);
                    Parameter oldParameter = parameters.get(i);
                    if(!newParameter.getTypeAsString().equals(oldParameter.getTypeAsString())){
                        break;
                    }
                    if(i == newParameterTypes.size()-1){
                        return methodDeclaration;
                    }
                }
            }
        }
        return null;
    }

    private boolean containMbgComment(String comment){
        if(comment == null){
            return false;
        }
        return comment.contains("@mbggenerated") || comment.contains("@mbg.generated");
    }

}
